package tool

import java.io.File
import java.nio.charset.StandardCharsets
import java.util.Locale
import cats.data.Validated
import dao.SampleDao
import shared.Pojo.{SeqData, StrReadsData}
import tool.Pojo.{BasicData, BatchMissionData, CommandData, ExportDatData, GenotypeData, MyDao, MyException, StrSite}

import scala.xml.{Node, XML}
import implicits.Implicits._
import org.joda.time.DateTime
import shared.VarTool
import tool.Tool._
import utils.Utils
import models.Tables._
import org.zeroturnaround.zip.ZipUtil
import play.api.libs.json.Json

import scala.collection.immutable.SeqMap
import scala.collection.parallel.CollectionConverters._
import scala.concurrent.ExecutionContext.Implicits.global

/**
 * Created by yz on 12/3/2020
 */
trait ProjectTool {

  def produceReadsDatas(sampleNode: Node) = {
    val id = Tool.parseGetId(sampleNode)
    case class StrSite(locus: String, productSize: String, genoType: String, typed: String, reads: String, snp: String)
    val strSites = (sampleNode \\ "strSites" \\ "site").toList.map { node =>
      val locus = (node \ "@locus").text
      val productSize = (node \ "@product_size").text
      val genoType = (node \ "Genotype").text
      val reads = (node \ "Reads").text.toDouble.toInt
      val typed = (node \ "Typed").text
      val snp = (node \ "snp").text
      StrSite(locus, productSize, genoType, typed, reads.toString, snp)
    }.filter(_.typed.equalsIgnoreCase("yes"))
    case class StrData(key: (String, String, String), kind: String, qc: String, level: String = "")
    val autoStrs = (sampleNode \\ "Autosomal_STR" \\ "autoStr").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      val (qc, level) = getQcLevel(autoNode)
      (autoNode \\ "genotype").map { node =>
        val snp = (node \ "@snp").text
        val t = (locus, node.text, snp)
        StrData(t, "Autosomal", qc, level)
      }
    }
    val yStrs = (sampleNode \\ "Y_STR" \\ "yStr").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      val (qc, level) = getQcLevel(autoNode)
      (autoNode \\ "genotype").map { node =>
        val snp = (node \ "@snp").text
        val t = (locus, node.text, snp)
        StrData(t, "Y", qc, level)
      }
    }
    val xStrs = (sampleNode \\ "X_STR" \\ "xStr").toList.flatMap { autoNode =>
      val locus = (autoNode \ "@Locus").text
      val (qc, level) = getQcLevel(autoNode)
      (autoNode \\ "genotype").map { node =>
        val snp = (node \ "@snp").text
        val t = (locus, node.text, snp)
        StrData(t, "X", qc, level)
      }
    }
    val strs = autoStrs ::: yStrs ::: xStrs
    val strMap = strs.map { str =>
      (str.key, str)
    }.toMap
    val locuses = strs.map(_.key._1).distinct
    val strSiteMap = strSites.map { site =>
      val t = (site.locus, site.genoType, site.snp)
      if (strMap.get(t).isEmpty) {
        throw new MyException(s"id(${id})、位点(${t._1})、分型(${t._2})、snp(${t._3}) 基本信息不存在！")
      }
      val str = strMap(t)
      StrReadsData(locus = t._1, genotype = t._2, reads = site.reads, qc = str.qc, productSize = site.productSize,
        kind = str.kind, level = str.level, snp = t._3)
    }.map(x => (x.locus, x)).groupSeqMap
    locuses.flatMap { locus =>
      strSiteMap(locus)
    }
  }

  def getSampleInfoFile(dir: File) = {
    new File(dir, "sampleInfo.xlsx")
  }

  def batchFys(workspaceDir: File, data: BatchMissionData)(implicit dao: MyDao) = {
    val splitResultDir = new File(data.splitDir)
    val fyWorkspaceDir = Tool.getFyWorkspaceDir(workspaceDir)
    val uploadSampleInfoFile = Tool.getSampleInfoFile(workspaceDir)
    val threadNum = Tool.getMissionDefaultThreadNum
    VarTool.kitNames.map { kitName =>
      val inFyWorkspaceDir = new File(fyWorkspaceDir, kitName)
      val listFile = new File(inFyWorkspaceDir, "sample.list")
      val maps = uploadSampleInfoFile.xlsxLines().lineMapNoLower.filter { map =>
        map("panel") == kitName
      }
      val indexMap = maps.map { map =>
        (map(VarTool.indexStr), map)
      }.toMap
      if (maps.nonEmpty) {
        val indexIds = maps.map(_ (VarTool.indexStr))
        val runIndexIds = splitResultDir.myListFiles.map { file =>
          file.getName.replaceAll("\\.fq\\.gz$", "").split("_").last
        }.filter { id =>
          indexIds.contains(id)
        }
        splitResultDir.myListFiles.filter { file =>
          val last = file.getName.replaceAll("\\.fq\\.gz$", "").split("_").last
          indexIds.contains(last)
        }.map { file =>
          file.getAbsolutePath.unixPath
        }.toFile(listFile)

        val xmlFile = new File(inFyWorkspaceDir, "sample.xml")
        val infoFile = new File(inFyWorkspaceDir, "sample.info")
        runIndexIds.map { id =>
          indexMap(id)
        }.map { map =>
          List(VarTool.indexStr, VarTool.sexStr, VarTool.kindStr).map { key =>
            map(key)
          }
        }.toTxtFile(infoFile)
        val reportDir = Tool.getReportDir(inFyWorkspaceDir)
        val outFile = Tool.getOutputXmlFile(inFyWorkspaceDir).myCreateNewFile
        val jarFile = kitName match {
          case "SETA" => s"${Tool.setAJarDir.unixPath}/setA.xjar"
          case "YARN" => s"${Tool.yarnJarDir.unixPath}/yarn.xjar"
          case "SETB" => s"${Tool.setBJarDir.unixPath}/setB.xjar"
        }
        val xJar = kitName match {
          case "SETA" => s"${Tool.setAJarDir.unixPath}/xjar"
          case "YARN" => s"${Tool.yarnJarDir.unixPath}/xjar"
          case "SETB" => s"${Tool.setBJarDir.unixPath}/xjar"
        }
        val command =
          s"""
             |#${Tool.fastxBinDir.unixPath}/fastx_quality_stats -i ${new File("").unixPath} -o stat.txt -Q 33
             |${xJar} java -jar ${jarFile} createXml  -i ${listFile.unixPath} -o ${xmlFile.unixPath} -sampleInfo ${infoFile.unixPath}
             |${xJar} java -jar ${jarFile} fastqToXml -i ${xmlFile.unixPath} -o ${outFile.unixPath} -worker ${threadNum} -useGivenGender -razorWorker 1
             |${xJar} java -jar ${jarFile} xmlToXlsx -i ${outFile.unixPath} -o ${reportDir.unixPath}
                       """.stripMargin
        CommandData(inFyWorkspaceDir, List(command))
      } else {
        CommandData(inFyWorkspaceDir, "")
      }
    }.par

  }

  def produceVennDataFile(workspaceDir: File) = {
    val configFile = Tool.getConfigFile(workspaceDir)
    val configJson = configFile.str.toJson
    val configData = FormTool.compareMissionForm.bind(configJson).get
    case class VennKey(locus: String, kind: String)
    val readsDatas = List(configData.sample1, configData.sample2).map {
      id =>
        val sampleIdDir = SampleTool.getSampleIdDir(configData.userId, id)
        val snpReadsFile = Tool.getSnpReadsFile(sampleIdDir)
        val snpReads = snpReadsFile.txtLines.drop(1).map {
          columns =>
            (columns(0), columns(1))
        }.groupSeqMap.map {
          case (locus, genotypes) =>
            val k = VennKey(locus, "snp")
            (k, genotypes.toSet)
        }
        val readsFile = Tool.getReadsFile(sampleIdDir)
        val reads = readsFile.txtLines.drop(1).map {
          columns =>
            (columns(0), columns(1))
        }.groupSeqMap.map {
          case (locus, genotypes) =>
            val k = VennKey(locus, "str")
            (k, genotypes.toSet)
        }
        (id, (snpReads ++ reads))
    }
    val interNum = readsDatas(0)._2.filter {
      case (k, genotypes) =>
        val otherData = readsDatas(1)._2
        val otherGenotypes = otherData.getOrElse(k, Set[String]())
        otherGenotypes == genotypes
    }.size
    val eachReads = readsDatas.map { x =>
      Json.obj("id" -> x._1, "num" -> x._2.size)
    }
    val totalJson = Json.obj("each" -> eachReads, "inter" -> interNum)
    val strEachReads = readsDatas.map { x =>
      Json.obj("id" -> x._1, "num" -> x._2.filter { case (k, v) => k.kind == "str" }.size)
    }
    val strInterNum = readsDatas(0)._2.filter {
      case (k, genotypes) =>
        k.kind == "str"
    }.filter { case (k, genotypes) =>
      val otherData = readsDatas(1)._2
      val otherGenotypes = otherData.getOrElse(k, Set[String]())
      otherGenotypes == genotypes
    }.size
    val strJson = Json.obj("each" -> strEachReads, "inter" -> strInterNum)
    val snpEachReads = readsDatas.map { x =>
      Json.obj("id" -> x._1, "num" -> x._2.filter { case (k, v) => k.kind == "snp" }.size)
    }
    val snpInterNum = readsDatas(0)._2.filter {
      case (k, genotypes) =>
        k.kind == "snp"
    }.filter { case (k, genotypes) =>
      val otherData = readsDatas(1)._2
      val otherGenotypes = otherData(k)
      otherGenotypes == genotypes
    }.size
    val snpJson = Json.obj("each" -> snpEachReads, "inter" -> snpInterNum)
    Json.obj("total" -> totalJson, "str" -> strJson, "snp" -> snpJson).toString().
      toFile(Tool.getVennDataFile(workspaceDir))
  }

  def produceDiffReadsDataFile(workspaceDir: File) = {
    val configFile = Tool.getConfigFile(workspaceDir)
    val configJson = configFile.str.toJson
    val configData = FormTool.compareMissionForm.bind(configJson).get
    val readsMaps = List(configData.sample1, configData.sample2).map { id =>
      val sampleIdDir = SampleTool.getSampleIdDir(configData.userId, id)
      val readsFile = Tool.getReadsFile(sampleIdDir)
      readsFile.txtLines.drop(1).map { columns =>
        (columns(0), columns(1))
      }.groupSeqMap.view.mapValues(_.toSet)
    }
    val diffReadsMap = readsMaps(0).filter { case (locus, genotypes) =>
      val otherReadsMap = readsMaps(1)
      val otherGenotypesOp = otherReadsMap.get(locus)
      otherGenotypesOp.map { otherGenotypes =>
        genotypes != otherGenotypes
      }.getOrElse(false)
    }.map { case (locus, genotypes) =>
      val otherReadsMap = readsMaps(1)
      val otherGenotypes = otherReadsMap(locus)
      Json.obj("locus" -> locus, "sample1" -> genotypes, "sample2" -> otherGenotypes)
    }
    Json.toJson(diffReadsMap).toString().toFile(Tool.getDiffReadsDataFile(workspaceDir))
  }

  def produceDiffSnpReadsDataFile(workspaceDir: File) = {
    val configFile = Tool.getConfigFile(workspaceDir)
    val configJson = configFile.str.toJson
    val configData = FormTool.compareMissionForm.bind(configJson).get
    val readsMaps = List(configData.sample1, configData.sample2).map { id =>
      val sampleIdDir = SampleTool.getSampleIdDir(configData.userId, id)
      val readsFile = Tool.getSnpReadsFile(sampleIdDir)
      readsFile.txtLines.drop(1).map { columns =>
        (columns(0), columns(1))
      }.groupSeqMap.view.mapValues(_.toSet)
    }
    val diffReadsMap = readsMaps(0).filter { case (locus, genotypes) =>
      val otherReadsMap = readsMaps(1)
      val otherGenotypes = otherReadsMap(locus)
      genotypes != otherGenotypes
    }.map { case (locus, genotypes) =>
      val otherReadsMap = readsMaps(1)
      val otherGenotypes = otherReadsMap(locus)
      Json.obj("locus" -> locus, "sample1" -> genotypes, "sample2" -> otherGenotypes)
    }
    Json.toJson(diffReadsMap).toString().toFile(Tool.getDiffSnpDataFile(workspaceDir))
  }

  def compareMission(workspaceDir: File)(implicit dao: MyDao) = {
    produceVennDataFile(workspaceDir)
    produceDiffReadsDataFile(workspaceDir)
    produceDiffSnpReadsDataFile(workspaceDir)
  }

  def getLevel(v: String) = {
    val dbs = v.split("/").map(_.toDouble)
    val rate = (dbs(0)) / (dbs(1))
    rate match {
      case x if x > 0.5 && x <= 0.8 => "中"
      case x if x <= 0.5 => "高"
      case x if x > 0.8 => "低"
      case _ => "低"
    }
  }

  def resultDealTest(workspaceDir: File) = {
    val xmlFile = new File(workspaceDir, "output.xml")
    if (xmlFile.exists()) {
      val basics = produceBasicDatas(xmlFile)
      val readsDataMap = produceReadsDataMap(xmlFile)
      val seqDataMap = produceSeqDataMap(xmlFile)
      val rawSeqDataMap = produceRawSeqDataMap(xmlFile)
      val snpReadsDataMap = produceSnpReadsDataMap(xmlFile)
      val snpSeqDataMap = produceSnpSeqDataMap(xmlFile)
      val basicDataMap = produceBasicDataMap(xmlFile)
      val strGenoTypeDataMap = produceStrGenotypeDataMap(xmlFile)
      val snpGenoTypeDataMap = produceSnpGenotypeDataMap(xmlFile)
      case class CurSeqData(genotype: String, typedAllele: String, reads: String, repeatSeq: String)
      basics.foreach { basic =>
        val id = basic.id
        val strGenotypeDatas = strGenoTypeDataMap(id)
        val snpGenotypeDatas = snpGenoTypeDataMap(id)
        val rawStrSeqDatas = rawSeqDataMap(id)
        val rawSnpSeqDatas = snpSeqDataMap(id)
        val rawSeqDatasLocusMap = (rawStrSeqDatas ::: rawSnpSeqDatas).groupBy { seqData =>
          seqData.locus
        }
        val siteJson = (strGenotypeDatas ::: snpGenotypeDatas).map { genotypeData =>
          val locus = genotypeData.locus
          Map(locus -> genotypeData.genoType)
        }.reduceLeft((x, y) => x ++ y)
        val seqJson = (strGenotypeDatas ::: snpGenotypeDatas).map { genotypeData =>
          val locus = genotypeData.locus
          val curRawSeqDatas = rawSeqDatasLocusMap.getOrElse(locus, List[SeqData]()).map { seqData =>
            CurSeqData(genotype = seqData.genotype, typedAllele = seqData.typedAllele, seqData.reads, seqData.repeatSeq)
          }
          val basicJson = Json.obj("rawSeqs" -> Utils.getJsonByTs(curRawSeqDatas))
          val json = if (genotypeData.qc.nonEmpty) {
            basicJson ++ Json.obj("qc" -> genotypeData.qc)
          } else basicJson
          Json.obj(locus -> json)
        }.reduceLeft((x, y) => x ++ y)

        val basicData = basicDataMap(id)
        val firstBasicData = basicData.head
        val autosomalLociTypedMap = Map("value" -> firstBasicData.autosomalLociTyped,
          "level" -> Tool.getLevel(firstBasicData.autosomalLociTyped))
        val yLociTypedMap = Map("value" -> firstBasicData.yLociTyped,
          "level" -> Tool.getLevel(firstBasicData.yLociTyped))
        val xLociTypedMap = Map("value" -> firstBasicData.xLociTyped,
          "level" -> Tool.getLevel(firstBasicData.xLociTyped))
        val iSNPLociTypedMap = Map("value" -> firstBasicData.iSNPLociTyped,
          "level" -> Tool.getLevel(firstBasicData.iSNPLociTyped))
        val sampleIdDir = new File(workspaceDir, basic.id)
        val readsData = readsDataMap(id)
        Utils.getLinesByTs(readsData).toTxtFile(Tool.getReadsFile(sampleIdDir))
        val snpReadsData = snpReadsDataMap(id)
        Utils.getLinesByTs(snpReadsData).toTxtFile(Tool.getSnpReadsFile(sampleIdDir))
        Tool.toBasicFile(basicData, sampleIdDir)
        Utils.getLinesByTs(strGenotypeDatas).toTxtFile(Tool.getStrGenotypeFile(sampleIdDir))
        Utils.getLinesByTs(snpGenotypeDatas).toTxtFile(Tool.getSnpGenotypeFile(sampleIdDir))
      }
    }
  }

  def resultDeal(workspaceDir: File, mission: MissionRow, sampleDao: SampleDao) = {
    val fyDir = Tool.getFyWorkspaceDir(workspaceDir)
    val lowLevelSamples = VarTool.kitNames.flatMap { kitName =>
      val inFyDir = new File(fyDir, kitName)
      val xmlFile = new File(inFyDir, "output.xml")
      if (xmlFile.exists()) {
        val basics = produceBasicDatas(xmlFile)
        val basicMap = basics.map { basic =>
          (basic.id, basic)
        }.toMap
        val readsDataMap = produceReadsDataMap(xmlFile)
        val seqDataMap = produceSeqDataMap(xmlFile)
        val rawSeqDataMap = produceRawSeqDataMap(xmlFile)
        val snpReadsDataMap = produceSnpReadsDataMap(xmlFile)
        val snpSeqDataMap = produceSnpSeqDataMap(xmlFile)
        val basicDataMap = produceBasicDataMap(xmlFile)
        val strGenoTypeDataMap = produceStrGenotypeDataMap(xmlFile)
        val snpGenoTypeDataMap = produceSnpGenotypeDataMap(xmlFile)
        val reportDir = Tool.getReportDir(inFyDir)
        case class CurSeqData(genotype: String, typedAllele: String, reads: String, repeatSeq: String, snp: String)
        val sampleInfoMap = Tool.getSampleInfoFile(workspaceDir).xlsxLines().lineMapNoLower.map { map =>
          (map(VarTool.indexStr), map)
        }.toSeqMap

        val lowLevelSamples = sampleInfoMap.filter { case (id, curMap) =>
          !basicMap.isDefinedAt(id)
        }
        sampleInfoMap.filter { case (id, curMap) =>
          !lowLevelSamples.isDefinedAt(id)
        }.foreach { case (id, curMap) =>
          val basic = basicMap(id)
          val indexId = curMap("样品编号")
          val strGenotypeDatas = strGenoTypeDataMap(id)
          val snpGenotypeDatas = snpGenoTypeDataMap(id)
          val rawStrSeqDatas = rawSeqDataMap(id)
          val rawSnpSeqDatas = snpSeqDataMap(id)
          val seqData = seqDataMap(id)
          val rawSeqDatasLocusMap = (rawStrSeqDatas ::: rawSnpSeqDatas).groupBy { seqData =>
            seqData.locus
          }
          val siteJson = (strGenotypeDatas ::: snpGenotypeDatas).map { genotypeData =>
            val locus = genotypeData.locus
            Map(locus -> genotypeData.genoType)
          }.reduceLeft((x, y) => x ++ y)
          val seqJson = (strGenotypeDatas ::: snpGenotypeDatas).map { genotypeData =>
            val locus = genotypeData.locus
            val curRawSeqDatas = rawSeqDatasLocusMap.getOrElse(locus, List[SeqData]()).map { seqData =>
              CurSeqData(seqData.genotype, seqData.typedAllele, seqData.reads, seqData.repeatSeq, snp = seqData.snp)
            }
            val basicJson = Json.obj("rawSeqs" -> Utils.getJsonByTs(curRawSeqDatas))
            val json = if (genotypeData.qc.nonEmpty) {
              basicJson ++ Json.obj("qc" -> genotypeData.qc)
            } else basicJson
            Json.obj(locus -> json)
          }.reduceLeft((x, y) => x ++ y)

          val basicData = basicDataMap(id)
          val firstBasicData = basicData.head
          val autosomalLociTypedMap = Map("value" -> firstBasicData.autosomalLociTyped,
            "level" -> Tool.getLevel(firstBasicData.autosomalLociTyped))
          val yLociTypedMap = Map("value" -> firstBasicData.yLociTyped,
            "level" -> Tool.getLevel(firstBasicData.yLociTyped))
          val xLociTypedMap = Map("value" -> firstBasicData.xLociTyped,
            "level" -> Tool.getLevel(firstBasicData.xLociTyped))
          val iSNPLociTypedMap = Map("value" -> firstBasicData.iSNPLociTyped,
            "level" -> Tool.getLevel(firstBasicData.iSNPLociTyped))
          val sample = SampleRow(indexId, mission.userId, curMap("板号"), id, curMap("项目"), curMap("性别"), basic.gener,
            curMap("类型"), curMap("panel"), curMap.getOrElse("备注", ""), mission.missionName, new DateTime(),
            firstBasicData.interlocusBalance, firstBasicData.singleSource, siteJson, seqJson, autosomalLociTypedMap,
            yLociTypedMap, xLociTypedMap, iSNPLociTypedMap
          )
          val f = sampleDao.insertOrUpdate(sample).map { x =>
            val sampleIdDir = SampleTool.getSampleIdDir(sample)
            val destFile = new File(sampleIdDir, s"${indexId}.xlsx").deleteIfExist
            reportDir.myListFiles.filter(_.getName.fileNamePrefix.endsWith(s"_${id}")).foreach { file =>
              file.copyTo(destFile)
            }
            val readsData = readsDataMap(id)
            Utils.getLinesByTs(readsData).toTxtFile(Tool.getReadsFile(sampleIdDir))
            val snpReadsData = snpReadsDataMap(id)
            Utils.getLinesByTs(snpReadsData).toTxtFile(Tool.getSnpReadsFile(sampleIdDir))
            Tool.toBasicFile(basicData, sampleIdDir)
            Utils.getLinesByTs(strGenotypeDatas).toTxtFile(Tool.getStrGenotypeFile(sampleIdDir))
            Utils.getLinesByTs(snpGenotypeDatas).toTxtFile(Tool.getSnpGenotypeFile(sampleIdDir))
          }
          Utils.execFuture(f)
        }
        lowLevelSamples
      } else {
        SeqMap.empty
      }
    }
    if (lowLevelSamples.size > 0) {
      val line1 = s"\n此次分析共过滤样本${lowLevelSamples.size}个，详细信息如下："
      val lines = line1 :: lowLevelSamples.zipWithIndex.map { case ((id, map), i) =>
        s"样本${i + 1}:index号为${id},样品编号为${map("样品编号")}"
      }
      val logFile = new File(workspaceDir.getParentFile, "log.txt")
      lines.toFile(logFile, append = true)
    }
  }

  def produceBasicDatas(file: File) = {
    val xml = XML.loadFile(file)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val gender = (sampleNode \ "basicInfo" \ "gender").text
      val kind = (sampleNode \ "basicInfo" \ "type").text
      val lane = (sampleNode \ "basicInfo" \ "lane").text
      BasicData(id, lane, gender, kind)
    }
  }

  def parseGetId(sampleNode: Node) = {
    (sampleNode \ "basicInfo" \ "id").text
  }

  def produceStrGenotypeDataMap(xmlFile: File) = {
    val xml = XML.loadFile(xmlFile)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val datas = Tool.produceStrGenotypeData(sampleNode)
      (id, datas)
    }.toSeqMap
  }

  def getQcLevel(qcPNode: Node) = {
    val qcNode = (qcPNode \\ "QC")
    val qc = qcNode.map { node =>
      node.text
    }.mkString(";")
    val level = qcNode.map { node =>
      (node \ "@level").text
    }.mkString(";")
    (qc, level)
  }

  def produceStrGenotypeData(sampleNode: Node) = {
    val autoStrs = (sampleNode \\ "Autosomal_STR" \\ "autoStr").toList.map { autoNode =>
      val locus = (autoNode \ "@Locus").text
      val (qc, level) = getQcLevel(autoNode)
      val genotype = (autoNode \ "Genotypes" \\ "genotype").toList.map(_.text).mkString(",")
      GenotypeData(locus, genotype, qc, level)
    }
    val yStrs = (sampleNode \\ "Y_STR" \\ "yStr").toList.map { yNode =>
      val locus = (yNode \ "@Locus").text
      val (qc, level) = getQcLevel(yNode)
      val genotype = (yNode \ "Genotypes" \\ "genotype").toList.map(_.text).mkString(",")
      GenotypeData(locus, genotype, qc, level)
    }
    val xStrs = (sampleNode \\ "X_STR" \\ "xStr").toList.map { xNode =>
      val locus = (xNode \ "@Locus").text
      val (qc, level) = getQcLevel(xNode)
      val genotype = (xNode \ "Genotypes" \\ "genotype").toList.map(_.text).mkString(",")
      GenotypeData(locus, genotype, qc, level)
    }
    autoStrs ::: yStrs ::: xStrs
  }

  def produceSnpGenotypeDataMap(xmlFile: File) = {
    val xml = XML.loadFile(xmlFile)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val datas = Tool.produceSnpGenotypeDatas(sampleNode)
      (id, datas)
    }.toMap
  }

  def produceSnpGenotypeDatas(sampleNode: Node) = {
    val id = Tool.parseGetId(sampleNode)
    (sampleNode \\ "iSNP" \\ "isnp").toList.map { snpNode =>
      val locus = (snpNode \ "@Locus").text
      val (qc, level) = getQcLevel(snpNode)
      val genotype = (snpNode \\ "genotype").map { node =>
        node.text
      }.mkString(",")
      GenotypeData(locus, genotype, qc, level)
    }
  }

  def getGenotypeSameInfo(posSites: List[PosSiteRow], file: File, sample: SampleRow) = {
    val kind = VarTool.genderMap.getOrElse(sample.gender, sample.gender)
    val value = posSites.find { x =>
      x.sex == kind
    }.get.value
    val dbDataMap = Json.parse(value).as[Map[String, String]]
    val dataMap = file.txtLines.lineMapNoLower.map { map =>
      map("locus") -> map("genoType")
    }.toMap
    val sameGenotypes = dbDataMap.filter { case (dbLocus, dbGenotype) =>
      val genotype = dataMap.getOrElse(dbLocus, "")
      genotype.split(",").toSet == dbGenotype.split(",").toSet
    }
    Json.obj("totalNum" -> dbDataMap.size, "sameNum" -> sameGenotypes.size, "id" -> sample.id)
  }

  def getNegGenotypeSameInfo(file: File, sample: SampleRow) = {
    val dataMap = file.txtLines.lineMapNoLower.map { map =>
      map("locus") -> map("genoType")
    }.toMap
    val sameGenotypes = dataMap.filter { case (locus, genotype) =>
      genotype.nonEmpty
    }
    Json.obj("totalNum" -> dataMap.size, "sameNum" -> sameGenotypes.size, "id" -> sample.id)
  }

  def produceRawSeqDataMap(file: File) = {
    val xml = XML.loadFile(file)
    (xml \\ "sample").toList.map { sampleNode =>
      val id = Tool.parseGetId(sampleNode)
      val seqDatas = Tool.produceRawSeqDatas(sampleNode)
      (id, seqDatas)
    }.toMap
  }

  def produceRawSeqDatas(sampleNode: Node) = {
    val strSites = (sampleNode \\ "strSites" \\ "site").toList.map { node =>
      val locus = (node \ "@locus").text
      val genoType = (node \ "Genotype").text
      val reads = (node \ "Reads").text.toDouble.toInt
      val typed = (node \ "Typed").text
      val repeatSeq = (node \ "RepeatSequence").text
      val snp = (node \ "snp").text
      StrSite(locus, genoType, typed, reads.toString, repeatSeq, snp = snp)
    }
    strSites.map { row =>
      SeqData(row.locus, row.genoType, row.typed, row.reads, row.repeatSeq, snp = row.snp)
    }
  }

  def dirCheck(gzPath: String, tmpBatchData: BatchMissionData) = {
    val gzDir = new File(gzPath)
    val splitDir = new File(gzDir, tmpBatchData.splitDir)
    val valid = splitDir.listFiles().filter(_.endsWith(Tool.splitFileSuffix)).nonEmpty
    Validated.cond(valid, true, s"已选目录下不存在.fq.gz文件，请检查数据目录是否正确！")
  }

  def xml2Dat(xmlFile: File, datFile: File, kitName: String) = {
    val siteNames = kitName match {
      case "geno" => VarTool.siteNamesMap("GenoY41")
      case "power" => List("AMEL", "D3S1358", "D1S1656", "D6S1043", "D13S317", "Penta E", "D16S539", "D18S51",
        "D2S1338", "CSF1PO", "Penta D", "TH01", "vWA", "D21S11", "D7S820", "D5S818", "TPOX", "D8S1179", "D12S391",
        "D19S433", "FGA")
      case "vers" => List("AMEL", "D3S1358", "D1S1656", "D2S441", "D10S1248", "D13S317", "Penta E", "D16S539",
        "D18S51", "D2S1338", "CSF1PO", "Penta D", "TH01", "vWA", "D21S11", "D7S820", "D5S818", "TPOX", "D8S1179",
        "D12S391", "D19S433", "D6S1043", "D22S1045", "DYS391", "FGA", "DYS576", "DYS570")
    }
    val xml = XML.loadFile(xmlFile)
    val headers =
      s"""
         |1.0
         |1
         |IMPORT
         |srclab
         |destlab
         |""".stripMargin.toLines
    val now = new DateTime()
    val time = now.toString("dd-MMM-yyyy HH:mm:ss", Locale.US)
    val hour = now.toString("HH:mm:ss.S", Locale.US)
    val content1s =
      s"""
         |AB
         |GeneMapper ID-X
         |""".stripMargin.toLines
    val strGenoTypeDataMap = Tool.produceStrGenotypeDataMap(xmlFile)
    val sampleNum = strGenoTypeDataMap.size
    val parent = new File("C:\\forensic_database\\test")
    val file = new File(parent, "V300036935肇庆血卡信息.xlsx")
    val idMap = file.xlsxLines().map { columns =>
      (columns(0) -> columns(5))
    }.toMap
    val sampleContents = strGenoTypeDataMap.flatMap { case (id, genotypeDatas) =>
      val headers =
        s"""
           |DNA Analysis Result
           |1.0
           |PCR
           |${idMap(id)}
           |0
           |Suspect, Known
           |unspecified
           |unspecified
           |unspecified
           |${siteNames.size}
           |""".stripMargin.toLines
      val genotypeMap = genotypeDatas.map { genotype =>
        (genotype.locus, genotype.genoType)
      }.toMap
      val genotypeInfos = siteNames.flatMap { siteName =>
        val siteNameMap = Map("AMEL" -> "Amelogenin", "Penta E" -> "Penta-E", "Penta D" -> "Penta-D",
          "DYS385" -> "DYS385a/b", "DYS527" -> "DYS527a/b", "DYF387S1" -> "DYF387S1a/b", "YGATAH4" -> "Y-GATA-H4")
        val trueSiteName = siteNameMap.getOrElse(siteName, siteName)
        val genotypes = if (VarTool.emptySiteNames.contains(trueSiteName)) {
          List[String]()
        } else genotypeMap(trueSiteName).mySplit(",").notEmptyLines
        val infos =
          s"""
             |${siteName}
             |1
             |gmidx
             |${time}
             |${hour}
             |${genotypes.size}
             |""".stripMargin.toLines
        infos ::: genotypes
      }
      headers ::: genotypeInfos
    }.toList
    val newLines = headers ::: List(time) ::: content1s ::: List(sampleNum.toString) ::: sampleContents
    newLines.notEmptyLines.toFile(datFile)
  }

  def getSplitSiteNames(siteNames: List[String]) = {
    siteNames.flatMap { siteName =>
      if (siteName.endsWith("a/b")) {
        val prefix = siteName.replaceAll("a/b$", "")
        List((prefix, s"${prefix}a"), (prefix, s"${prefix}b"))
      } else List((siteName, siteName))
    }
  }

  def getMaxGenoTypes(genoTypes: List[String]) = {
    if (genoTypes.isEmpty) {
      genoTypes
    } else {
      val max = genoTypes.maxBy { x =>
        x.toDouble
      }
      List(max)
    }
  }

  def getMinGenoTypes(genoTypes: List[String]) = {
    if (genoTypes.isEmpty) {
      genoTypes
    } else {
      val min = genoTypes.minBy { x =>
        x.toDouble
      }
      List(min)
    }
  }

  def samples2Dat(tmpDir: File, samples: List[SampleRow], kitName: String, exportDatData: ExportDatData): Unit = {
    val outFile = new File(tmpDir, "out.file")
    if (exportDatData.hasGroup) {
      val outDir = new File(tmpDir, "out")
      samples.grouped(exportDatData.sampleNum.toInt).zipWithIndex.foreach { case (curSamples, i) =>
        val curOutFile = new File(outDir, s"part${i + 1}.dat")
        Tool.samples2Dat(curSamples, curOutFile, kitName, exportDatData)
      }
      ZipUtil.pack(outDir, outFile)
    } else {
      Tool.samples2Dat(samples, outFile, kitName, exportDatData)
    }

  }

  def getTrueSiteName(kitName: String, siteName: String) = {
    val siteNameMap = kitName match {
      case "GenoY41" => Map("Y-GATA-H4" -> "YGATAH4", "DYS385a/b" -> "DYS385", "DYS527a/b" -> "DYS527",
        "DYF387S1a/b" -> "DYF387S1")
      case "华夏白金" => Map("Amelogenin" -> "AMEL", "Penta-E" -> "Penta E", "Penta-D" -> "Penta D",
        "Y-indel" -> "Yindel")
      case "PowerPlex 21" => Map("Amelogenin" -> "AMEL", "Penta-E" -> "Penta E", "Penta-D" -> "Penta D")
      case "VersaPlex 27PY" => Map("Amelogenin" -> "AMEL", "Penta-E" -> "Penta E", "Penta-D" -> "Penta D")
      case "MR36A" => Map("Amelogenin" -> "Amel", "Penta-D" -> "Penta D", "Penta-E" -> "Penta E")
      case "YSUPP" => Map("DYF404S1a/b" -> "DYF404S1", "Y-indel" -> "Yindel", "DYS527a/b" -> "DYS527",
        "DYS385a/b" -> "DYS385")
      case _ => Map[String, String]()
    }
    siteNameMap.getOrElse(siteName, siteName)
  }

  def samples2Dat(samples: List[SampleRow], datFile: File, kitName: String, exportDatData: ExportDatData): Unit = {
    val dbSiteNames = VarTool.siteNamesMap(kitName)
    val trueDbSiteNames = dbSiteNames.map { siteName =>
      getTrueSiteName(kitName, siteName)
    }
    val siteNames = if (exportDatData.isSplit) {
      getSplitSiteNames(trueDbSiteNames)
    } else trueDbSiteNames
    val headers =
      s"""
         |1.0
         |1
         |IMPORT
         |srclab
         |destlab
         |""".stripMargin.toLines
    val now = new DateTime()
    val time = now.toString("dd-MMM-yyyy HH:mm:ss", Locale.US)
    val hour = now.toString("HH:mm:ss.S", Locale.US)
    val content1s =
      s"""
         |AB
         |GeneMapper ID-X
         |""".stripMargin.toLines
    val sampleNum = samples.size
    val sampleContents = samples.flatMap { sample =>
      val headers =
        s"""
           |DNA Analysis Result
           |1.0
           |PCR
           |${sample.id}
           |0
           |Suspect, Known
           |unspecified
           |unspecified
           |unspecified
           |${siteNames.size}
           |""".stripMargin.toLines
      val siteMap = sample.siteInfo
      val genotypeMap = dbSiteNames.map { siteName =>
        val genotype = siteMap.getOrElse(siteName, "")
        (siteName -> genotype)
      }.toMap
      val genotypeInfos = dbSiteNames.flatMap { siteName =>
        val genotypes = if (VarTool.emptySiteNames.contains(siteNames)) {
          List[String]()
        } else genotypeMap(siteName).mySplit(",").notEmptyLines
        val trueSiteName = getTrueSiteName(kitName, siteName)
        val splitSiteNames = if (exportDatData.isSplit) getSplitSiteNames(List(trueSiteName)) else List((trueSiteName, trueSiteName))
        splitSiteNames.flatMap { case (prefix, splitSiteName) =>
          splitSiteName match {
            case x if x == prefix =>
              val infos =
                s"""
                   |${splitSiteName}
                   |1
                   |gmidx
                   |${time}
                   |${hour}
                   |${genotypes.size}
                   |""".stripMargin.toLines
              infos ::: genotypes
            case x if x == s"${prefix}a" =>
              val curGenoTypes = getMaxGenoTypes(genotypes)
              val infos =
                s"""
                   |${splitSiteName}
                   |1
                   |gmidx
                   |${time}
                   |${hour}
                   |${curGenoTypes.size}
                   |""".stripMargin.toLines
              infos ::: curGenoTypes
            case x if x == s"${prefix}b" =>
              val curGenoTypes = getMinGenoTypes(genotypes)
              val infos =
                s"""
                   |${splitSiteName}
                   |1
                   |gmidx
                   |${time}
                   |${hour}
                   |${curGenoTypes.size}
                   |""".stripMargin.toLines
              infos ::: curGenoTypes
          }
        }

      }
      headers ::: genotypeInfos
    }
    val newLines = headers ::: List(time) ::: content1s ::: List(sampleNum.toString) ::: sampleContents
    newLines.notEmptyLines.toFile(datFile, encoding = "Cp1252", lineEnding = "\r\n")
  }

  def getJsonBySample(y: SampleRow) = {
    val siteMap = y.siteInfo
    val seqJson = y.seqInfo
    val autosomalLociTypedMap = y.autosomalLociTyped
    val yLociTypedMap = y.yLociTyped
    val xLociTypedMap = y.xLociTyped
    val isnpLociTypedMap = y.isnpLociTyped
    val allJson = seqJson.toJsObject.value.map { case (field, jsValue) =>
      val newJV = jsValue.toJsObject ++ Json.obj("genotype" -> siteMap(field))
      (field, newJV)
    }
    Utils.getJsonByT(y).toJsObject - ("seqInfo") - ("siteInfo") - ("autosomalLociTyped") - ("yLociTyped") -
      ("xLociTyped") - ("isnpLociTyped") ++ Json.toJsObject(allJson) ++
      Json.obj("autosomalLociTyped" -> autosomalLociTypedMap("value"), "yLociTyped" -> yLociTypedMap("value"),
        "xLociTyped" -> xLociTypedMap("value"), "isnpLociTyped" -> isnpLociTypedMap("value"))
  }

  def getJsonBySampleWithStepNumOther(y: SampleRow, dbSample: SampleRow, kitName: String) = {
    val siteMap = y.siteInfo
    val seqJson = y.seqInfo
    val autosomalLociTypedMap = y.autosomalLociTyped
    val yLociTypedMap = y.yLociTyped
    val xLociTypedMap = y.xLociTyped
    val isnpLociTypedMap = y.isnpLociTyped
    val siteNames = VarTool.siteNamesMap(kitName.toUpperCase)
    val dbSiteInfo = dbSample.siteInfo
    val hasColorSiteNames = {
      siteNames.filter { siteName =>
        val v = siteMap.getOrElse(siteName, "")
        val dbV = dbSiteInfo.getOrElse(siteName, "")
        v.nonEmpty && dbV.nonEmpty && v != dbV
      }
    }
    val stepNum = hasColorSiteNames.size
    val allJson = seqJson.toJsObject.value.map { case (field, jsValue) =>
      val color = if (hasColorSiteNames.contains(field)) "#FABD82" else "white"
      val newJV = jsValue.toJsObject ++ Json.obj("genotype" -> siteMap(field), "color" -> color)
      (field, newJV)
    }
    val json = Utils.getJsonByT(y).toJsObject - ("seqInfo") - ("siteInfo") - ("autosomalLociTyped") - ("yLociTyped") -
      ("xLociTyped") - ("isnpLociTyped") ++ Json.toJsObject(allJson) ++
      Json.obj("autosomalLociTyped" -> autosomalLociTypedMap("value"), "yLociTyped" -> yLociTypedMap("value"),
        "xLociTyped" -> xLociTypedMap("value"), "isnpLociTyped" -> isnpLociTypedMap("value"), "stepNum" -> stepNum)
    (json, hasColorSiteNames)
  }

  def getJsonBySampleWithStepNumSelf(y: SampleRow, kitName: String, totalColorSiteNames: Set[String]) = {
    val siteMap = y.siteInfo
    val seqJson = y.seqInfo
    val autosomalLociTypedMap = y.autosomalLociTyped
    val yLociTypedMap = y.yLociTyped
    val xLociTypedMap = y.xLociTyped
    val isnpLociTypedMap = y.isnpLociTyped
    val siteNames = VarTool.siteNamesMap(kitName.toUpperCase)
    val hasColorSiteNames = totalColorSiteNames
    val stepNum = 0
    val allJson = seqJson.toJsObject.value.map { case (field, jsValue) =>
      val color = if (hasColorSiteNames.contains(field)) "#FABD82" else "white"
      val newJV = jsValue.toJsObject ++ Json.obj("genotype" -> siteMap(field), "color" -> color)
      (field, newJV)
    }
    Utils.getJsonByT(y).toJsObject - ("seqInfo") - ("siteInfo") - ("autosomalLociTyped") - ("yLociTyped") -
      ("xLociTyped") - ("isnpLociTyped") ++ Json.toJsObject(allJson) ++
      Json.obj("autosomalLociTyped" -> autosomalLociTypedMap("value"), "yLociTyped" -> yLociTypedMap("value"),
        "xLociTyped" -> xLociTypedMap("value"), "isnpLociTyped" -> isnpLociTypedMap("value"), "stepNum" -> stepNum)
  }


}
